﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml.Linq;
using System.Xml.Serialization;
using Duellum.Core.ExceptionHelper;
using JpLabs.DynamicCode;

namespace Duellum.Core
{
	///http://www.ondotnet.com/pub/a/dotnet/2002/08/26/serialization.html
	
	[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
	public class DuelTypeAttribute : Attribute
	{
		//public string DuelTypeId	{ get; set; }
		public XName XmlElementName	{ get; set; }
		
		//public DuelTypeAttribute(string duelTypeId)
		//{
		//    this.DuelTypeId = duelTypeId;
		//}

		//public DuelTypeAttribute(string duelTypeId, string xmlElementName)
		public DuelTypeAttribute(string xmlElementName)
		{
			//this.DuelTypeId = duelTypeId;
			this.XmlElementName = (XName)xmlElementName;
		}
	}

	public class DuelSerializer : IDisposable
	{
		private Compiler compiler;
		public Compiler Compiler
		{
			get {
				if (compiler == null) {
					compiler = new Compiler();
					compiler.AddTypeReference(typeof(AugObject));
				}
				return compiler;
			}
		}
		
		public void Dispose()
		{
			if (compiler != null) {
				compiler.Dispose();
				compiler = null;
			}
		}

		public DuelType DeserializeDuelType(DuelDomain domain, XElement x, params string[] namespaces)
		{
			//Create DuelType
		    DuelType duelType = domain.CreateDuelTypeFromXElement(x);
		    if (duelType == null) return null;
		    
		    //Parse Augmented Properties
		    Type baseSystemType = duelType.GetType();
	        foreach (var attr in x.Attributes().Where(a => a.Name != "id")) {
				var propFullName = attr.Name.ToString();
				
				if (propFullName == "base") {
					duelType.SetBaseTypeId(attr.Value);
				} else {
					var prop = FindProperty(AugDomain.Current, propFullName, baseSystemType, namespaces);
					if (prop == null) throw Error.DuelPropertyNotFound(propFullName);
		            
					duelType.SetValue(prop, attr.Value);
				}
	        }
			
			//Parse Lambda-Properties
			if (x.HasElements) {
				foreach (var child in x.Elements()) {
					var propFullName = child.Name.ToString();
					
					var prop = FindProperty(AugDomain.Current, propFullName, baseSystemType, namespaces);
					if (prop == null) throw Error.DuelPropertyNotFound(propFullName);
		            
		            LambdaExpression funcValue = this.Compiler.ParseLambdaExpr(child.Value);
					duelType.CombineValue(prop, funcValue);
				}
			}
			
	        return duelType;
		}
		
		static private AugProperty FindProperty(AugDomain augDomain, string propFullName, Type defaultOwnerType, params string[] namespaces)
		{
			string ownerTypeName;
			var propName = GetPropertyName(propFullName, out ownerTypeName);
			
			if (string.IsNullOrEmpty(ownerTypeName)) {
				return augDomain.FindProp(propName, defaultOwnerType);
			} else {
				return augDomain.FindProp(propName, ownerTypeName, namespaces);
			}
		}
		
		static public string GetPropertyName(string xmlAttrName, out string namespc)
		{
			int dotPos = xmlAttrName.LastIndexOf('.');
			if (dotPos != -1) {
				namespc = xmlAttrName.Substring(0, dotPos);
				return xmlAttrName.Substring(Math.Min(dotPos + 1, xmlAttrName.Length));
			} else {
				namespc = null;
				return xmlAttrName;
			}
		}

		static public string SerializeAugObj(AugObject obj)
		{
			if (obj == null) throw Error.ArgumentNull("obj");
			
			return obj.ToXml().ToString();
		}

		//static XElement AugObjToXElement(AugObject obj)
		//{
		//    var xElem = new XElement(GetTypeName(obj));
			
		//    //xElem.Add(new XAttribute("id", GetTypeId(obj)));
		//    if (obj is DuelType) xElem.Add(new XAttribute("id", ((DuelType)obj).Id.ToString()));
			
		//    xElem.Add(obj.GetLocalValues().Select(kv => new XAttribute(kv.Key.Name, kv.Value.ToString())));
			
		//    if (obj.AugParent != null) xElem.Add(AugObjToXElement(obj.AugParent));
			
		//    return xElem;
		//}

		//static string GetTypeName(AugObject obj)
		//{
		//    //if (obj is DuelObj)	return ((DuelObj)obj).Type.GetType().Name;
		//    return obj.GetType().Name;
		//}

		//static string GetTypeId(AugObject obj)
		//{
		//    if (obj is DuelObj)		return ((DuelObj)obj).Type.Id.ToString();
		//    if (obj is DuelType)	return ((DuelType)obj).Id.ToString();
		//    return null;
		//}
	}
}
